// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/pending-compilation-error-handler.h"

#include "src/ast/ast-value-factory.h"
#include "src/debug/debug.h"
#include "src/handles.h"
#include "src/isolate.h"
#include "src/messages.h"
#include "src/objects-inl.h"

namespace v8 {
namespace internal {

    Handle<String> PendingCompilationErrorHandler::MessageDetails::ArgumentString(
        Isolate* isolate) const
    {
        if (arg_ != nullptr)
            return arg_->string();
        if (char_arg_ != nullptr) {
            return isolate->factory()
                ->NewStringFromUtf8(CStrVector(char_arg_))
                .ToHandleChecked();
        }
        return isolate->factory()->undefined_string();
    }

    MessageLocation PendingCompilationErrorHandler::MessageDetails::GetLocation(
        Handle<Script> script) const
    {
        return MessageLocation(script, start_position_, end_position_);
    }

    void PendingCompilationErrorHandler::ReportMessageAt(
        int start_position, int end_position, MessageTemplate message,
        const char* arg, ParseErrorType error_type)
    {
        if (has_pending_error_)
            return;
        has_pending_error_ = true;

        error_details_ = MessageDetails(start_position, end_position, message, nullptr, arg);
        error_type_ = error_type;
    }

    void PendingCompilationErrorHandler::ReportMessageAt(
        int start_position, int end_position, MessageTemplate message,
        const AstRawString* arg, ParseErrorType error_type)
    {
        if (has_pending_error_)
            return;
        has_pending_error_ = true;

        error_details_ = MessageDetails(start_position, end_position, message, arg, nullptr);
        error_type_ = error_type;
    }

    void PendingCompilationErrorHandler::ReportWarningAt(int start_position,
        int end_position,
        MessageTemplate message,
        const char* arg)
    {
        warning_messages_.emplace_front(
            MessageDetails(start_position, end_position, message, nullptr, arg));
    }

    void PendingCompilationErrorHandler::ReportWarnings(Isolate* isolate,
        Handle<Script> script)
    {
        DCHECK(!has_pending_error());

        for (const MessageDetails& warning : warning_messages_) {
            MessageLocation location = warning.GetLocation(script);
            Handle<String> argument = warning.ArgumentString(isolate);
            Handle<JSMessageObject> message = MessageHandler::MakeMessageObject(isolate, warning.message(), &location,
                argument, Handle<FixedArray>::null());
            message->set_error_level(v8::Isolate::kMessageWarning);
            MessageHandler::ReportMessage(isolate, &location, message);
        }
    }

    void PendingCompilationErrorHandler::ReportErrors(
        Isolate* isolate, Handle<Script> script,
        AstValueFactory* ast_value_factory)
    {
        if (stack_overflow()) {
            isolate->StackOverflow();
        } else {
            DCHECK(has_pending_error());
            // Internalize ast values for throwing the pending error.
            ast_value_factory->Internalize(isolate);
            ThrowPendingError(isolate, script);
        }
    }

    void PendingCompilationErrorHandler::ThrowPendingError(Isolate* isolate,
        Handle<Script> script)
    {
        if (!has_pending_error_)
            return;

        MessageLocation location = error_details_.GetLocation(script);
        Handle<String> argument = error_details_.ArgumentString(isolate);
        isolate->debug()->OnCompileError(script);

        Factory* factory = isolate->factory();
        Handle<Object> error;
        switch (error_type_) {
        case kReferenceError:
            error = factory->NewReferenceError(error_details_.message(), argument);
            break;
        case kSyntaxError:
            error = factory->NewSyntaxError(error_details_.message(), argument);
            break;
        default:
            UNREACHABLE();
            break;
        }

        if (!error->IsJSObject()) {
            isolate->Throw(*error, &location);
            return;
        }

        Handle<JSObject> jserror = Handle<JSObject>::cast(error);

        Handle<Name> key_start_pos = factory->error_start_pos_symbol();
        Object::SetProperty(isolate, jserror, key_start_pos,
            handle(Smi::FromInt(location.start_pos()), isolate),
            StoreOrigin::kMaybeKeyed,
            Just(ShouldThrow::kThrowOnError))
            .Check();

        Handle<Name> key_end_pos = factory->error_end_pos_symbol();
        Object::SetProperty(isolate, jserror, key_end_pos,
            handle(Smi::FromInt(location.end_pos()), isolate),
            StoreOrigin::kMaybeKeyed,
            Just(ShouldThrow::kThrowOnError))
            .Check();

        Handle<Name> key_script = factory->error_script_symbol();
        Object::SetProperty(isolate, jserror, key_script, script,
            StoreOrigin::kMaybeKeyed,
            Just(ShouldThrow::kThrowOnError))
            .Check();

        isolate->Throw(*error, &location);
    }

    Handle<String> PendingCompilationErrorHandler::FormatErrorMessageForTest(
        Isolate* isolate) const
    {
        return MessageFormatter::Format(isolate, error_details_.message(),
            error_details_.ArgumentString(isolate));
    }

} // namespace internal
} // namespace v8
